/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Insets; import java.awt.Shape; import java.awt.Point; import java.awt.Font; import java.awt.Color; import java.awt.Cursor; import java.awt.event.ActionEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; import java.io.Serializable; import java.io.ObjectInputStream; import java.io.IOException; import java.util.List; import java.util.Iterator; import javax.swing.text.*; import javax.swing.event.DocumentListener; import javax.swing.event.DocumentEvent; import javax.swing.plaf.TextUI; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.UIManager; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.SwingUtilities; import javax.swing.Action; import javax.swing.border.Border; /** * Text UI implementation * * @author Miloslav Metelka * @version 1.00 */ public class BaseTextUI extends TextUI implements ViewFactory, PropertyChangeListener, DocumentListener { /** Get rid of mantisa problems */ private static final int MAX_SPAN = Integer.MAX_VALUE - 512; /** Minimum component width */ private static final int MIN_WIDTH = 300; /** Minimum component height */ private static final int MIN_HEIGHT = 200; /** Editor component */ private JTextComponent component; /** Extended UI */ private ExtUI extUI; /** Instance of the <tt>GetFocusedComponentAction</tt> */ private static final GetFocusedComponentAction gfcAction = new GetFocusedComponentAction(); /** Root view of view hierarchy */ private RootView rootView; public BaseTextUI() { rootView = new RootView(); } static JTextComponent getFocusedComponent() { return gfcAction.getFocusedComponent2(); } /** Called when the model of component is changed */ protected void modelChanged(BaseDocument oldDoc, BaseDocument newDoc) { if (oldDoc != null) { oldDoc.removeDocumentListener(this); } if (newDoc != null) { newDoc.addDocumentListener(this); ViewFactory f = rootView.getViewFactory(); BaseKit kit = (BaseKit)getEditorKit(component); component.removeAll(); Element elem = newDoc.getDefaultRootElement(); View v = f.create(elem); rootView.setView(v); rootView.updateMainHeight(); // compute actual height of views component.revalidate(); // Execute actions related to document installaction into the component Settings.KitAndValue[] kv = Settings.getKitAndValueArray(kit.getClass(), Settings.DOC_INSTALL_ACTION_NAME_LIST); for (int i = kv.length - 1; i >= 0; i--) { List actList = (List)kv[i].value; actList = kit.translateActionNameList(actList); // translate names to actions if (actList != null) { for (Iterator iter = actList.iterator(); iter.hasNext();) { Action a = (Action)iter.next(); a.actionPerformed(new ActionEvent(component, ActionEvent.ACTION_PERFORMED, "")); // NOI18N } } } } } /** Update height of the views */ void updateHeight() { rootView.updateMainHeight(); } /** Installs the UI for a component. */ public void installUI(JComponent c) { if (c instanceof JTextComponent) { component = (JTextComponent) c; // this is associated component getExtUI().installUI(component); component.setOpaque(true); // opaque by default component.setAutoscrolls(true); // autoscrolling by default // attach to the model and component component.addPropertyChangeListener(this); component.addPropertyChangeListener(new UIWatcher(this.getClass())); BaseKit kit = (BaseKit)getEditorKit(component); // Create and attach caret Caret caret = kit.createCaret(); component.setCaretColor(Color.black); // will be changed by settings later component.setCaret(caret); // assign blink rate int br = SettingsUtil.getInteger(Utilities.getKitClass(component), Settings.CARET_BLINK_RATE, DefaultSettings.defaultCaretBlinkRate.intValue()); caret.setBlinkRate(br); // Create document Document doc = component.getDocument(); if (doc instanceof BaseDocument) { modelChanged(null, (BaseDocument)doc); } /** Patch for 1.3 - assigns a null UI input map into the component. * The following block stands for the following code: * * SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null); * */ try { Class inputMapClass = Class.forName("javax.swing.InputMap"); // NOI18N if (inputMapClass != null) { java.lang.reflect.Method replaceUIInputMapMethod = SwingUtilities.class.getDeclaredMethod( "replaceUIInputMap", new Class[] { JComponent.class, Integer.TYPE, inputMapClass }); // NOI18N replaceUIInputMapMethod.invoke(null, new Object[] { c, new Integer(JComponent.WHEN_FOCUSED), null }); } } catch (Throwable t) { } Keymap km = kit.getKeymap(); component.setKeymap(km); Registry.addComponent(component); component.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); } } /** Deinstalls the UI for a component */ public void uninstallUI(JComponent c) { component.removePropertyChangeListener(this); component.getDocument().removeDocumentListener(this); rootView.setView(null); // sets inner view of root view to null component.removeAll(); component.setKeymap(null); getExtUI().uninstallUI(component); // Clear the extUI so it will be recreated according to the kit // of the component for which the installUI is called extUI = null; } /** Paint the UI. * * @param g the graphics context * @param c the editor component */ public void paint(Graphics g, JComponent c) { if ((rootView.getViewCount() > 0) && (rootView.getView(0) != null)) { BaseDocument doc = (BaseDocument)component.getDocument(); doc.readLock(); try { // paint the view hierarchy getExtUI().paint(g); // paint the caret Caret caret = component.getCaret(); if (caret != null) { caret.paint(g); } // check virtual size change if (getExtUI().virtualSizeUpdated) { preferenceChanged(true, true); } } finally { doc.readUnlock(); } } } /** Paint either image region or classic graphics region */ void paintRegion(Graphics g) { rootView.paint(g, null); } /** Gets the preferred size for the editor component. If the component * has been given a size prior to receiving this request, it will * set the size of the view hierarchy to reflect the size of the component * before requesting the preferred size of the view hierarchy. This * allows formatted views to format to the current component size before * answering the request. Other views don't care about currently formatted * size and give the same answer either way. * * @param c the editor component * @return the size */ public Dimension getPreferredSize(JComponent c) { Insets i = c.getInsets(); Insets margin = getExtUI().getTextMargin(); Dimension d = c.getSize(); BaseDocument doc = (BaseDocument)component.getDocument(); doc.readLock(); try { // first try to change the root view size if ((d.width > (i.left + i.right)) && (d.height > (i.top + i.bottom))) { rootView.setSize(d.width - i.left - i.right, d.height - i.top - i.bottom); } // now get the real preferred size d.width = (int)Math.min(rootView.getPreferredSpan(View.X_AXIS) + i.left + i.right + margin.left + margin.right, MAX_SPAN); d.height = (int)Math.min(rootView.getPreferredSpan(View.Y_AXIS) + i.top + i.bottom + margin.top + margin.bottom, MAX_SPAN); } finally { doc.readUnlock(); } return d; } /** Gets the minimum size for the editor component. * * @param c the editor component * @return the size */ public Dimension getMinimumSize(JComponent c) { Insets i = c.getInsets(); Insets margin = getExtUI().getTextMargin(); Dimension d = new Dimension(); BaseDocument doc = (BaseDocument)component.getDocument(); doc.readLock(); try { d.width = (int) rootView.getMinimumSpan(View.X_AXIS) + i.left + i.right + margin.left + margin.right; d.height = (int) rootView.getMinimumSpan(View.Y_AXIS) + i.top + i.bottom + margin.top + margin.bottom; } finally { doc.readUnlock(); } return d; } /** Gets the maximum size for the editor component. * * @param c the editor component * @return the size */ public Dimension getMaximumSize(JComponent c) { Insets i = c.getInsets(); Insets margin = getExtUI().getTextMargin(); Dimension d = new Dimension(); BaseDocument doc = (BaseDocument)component.getDocument(); doc.readLock(); try { d.width = (int)Math.min(rootView.getMaximumSpan(View.X_AXIS) + i.left + i.right + margin.left + margin.right, MAX_SPAN); d.height = (int)Math.min(rootView.getMaximumSpan(View.Y_AXIS) + i.top + i.bottom + margin.top + margin.bottom, MAX_SPAN); } finally { doc.readUnlock(); } return d; } public void invalidateStartY() { rootView.invalidateStartY(); } /** Similair to modelToView() but without acquiring the document read lock. */ public Rectangle modelToView(JTextComponent c, int pos) throws BadLocationException { return (Rectangle)rootView.modelToView(pos, null, Position.Bias.Forward); } public Rectangle modelToView(JTextComponent c, int pos, Position.Bias bias) throws BadLocationException { return (Rectangle)rootView.modelToView(pos, null, bias); } public void modelToViewDG(int pos, Drawer.DrawGraphics dg) throws BadLocationException { rootView.modelToViewDG(pos, dg); } public int getYFromPos(int pos) throws BadLocationException { return rootView.getYFromPos(pos); } public int getPosFromY(int y) throws BadLocationException { return rootView.getPosFromY(y); } public int getBaseX(int y) { return rootView.getBaseX(y); } public int viewToModel(JTextComponent c, Point pt) { return viewToModel(c, pt.x, pt.y); } public int viewToModel(JTextComponent c, int x, int y) { return rootView.viewToModel(x, y, null, null); } public int viewToModel(JTextComponent c, Point pt, Position.Bias[] biasReturn) { return rootView.viewToModel(pt.x, pt.y, null, biasReturn); } /** Next visually represented model location where caret can be placed. * This version works without placing read lock on the document. */ public int getNextVisualPositionFrom(JTextComponent t, int pos, Position.Bias b, int direction, Position.Bias[] biasRet) throws BadLocationException{ return rootView.getNextVisualPositionFrom(pos, b, null, direction, biasRet); } public void damageRange(JTextComponent c, int p0, int p1) { damageRange(c, p0, p1, Position.Bias.Forward, Position.Bias.Backward); } /** Causes the portion of the view responsible for the * given part of the model to be repainted. * * @param p0 the beginning of the range >= 0 * @param p1 the end of the range >= p0 */ public void damageRange(JTextComponent t, int p0, int p1, Position.Bias p0Bias, Position.Bias p1Bias) { BaseDocument doc = (BaseDocument)component.getDocument(); doc.readLock(); try { Rectangle r = (Rectangle)rootView.modelToView(p0, p0Bias, p1, p1Bias, null); component.repaint(r.x, r.y, r.width, r.height); } catch (BadLocationException e) { } finally { doc.readUnlock(); } } /** Fetches the EditorKit for the UI. * * @return the component capabilities */ public EditorKit getEditorKit(JTextComponent c) { JEditorPane pane = (JEditorPane)component; return pane.getEditorKit(); } /** Fetches a root view of the view hierarchy. */ public View getRootView(JTextComponent c) { return rootView; } /** Get extended UI. This is called from views to get correct extended UI. */ public ExtUI getExtUI() { if (extUI == null) { BaseKit kit = (BaseKit)getEditorKit(component); extUI = kit.createExtUI(); } return extUI; } /** * This method gets called when a bound property is changed. * We are looking for document changes on the component. */ public void propertyChange(PropertyChangeEvent evt) { String propName = evt.getPropertyName(); if ("document".equals(propName)) { BaseDocument oldDoc = (evt.getOldValue() instanceof BaseDocument) ? (BaseDocument)evt.getOldValue() : null; BaseDocument newDoc = (evt.getNewValue() instanceof BaseDocument) ? (BaseDocument)evt.getNewValue() : null; modelChanged(oldDoc, newDoc); } } /** Insert to document notification. */ public void insertUpdate(DocumentEvent evt) { rootView.insertUpdate(evt, null, rootView.getViewFactory()); if (((BaseDocumentEvent)evt).getLFCount() > 0 || evt.getLength() == 0 ) { if (getExtUI().updateVirtualHeight(rootView.getHeight())) { preferenceChanged(true, true); } getExtUI().checkLineLimit(); if (evt.getLength() == 0) { // initial read performed !!! needed? component.repaint(500); } } } /** Remove from document notification. */ public void removeUpdate(DocumentEvent evt) { rootView.removeUpdate(evt, null, rootView.getViewFactory()); if (((BaseDocumentEvent)evt).getLFCount() > 0) { if (getExtUI().updateVirtualHeight(rootView.getHeight())) { preferenceChanged(true, true); } } } /** The change in document notification. * * @param e The change notification from the currently associated document. */ public void changedUpdate(DocumentEvent evt) { BaseDocumentEvent bdevt = (BaseDocumentEvent)evt; BaseDocument doc = (BaseDocument)bdevt.getDocument(); String layerName = bdevt.getDrawLayerName(); if (layerName != null) { getExtUI().addLayer(doc.findLayer(layerName)); } else { // some other type of change, propagate to root view rootView.changedUpdate(evt, null, rootView.getViewFactory()); } } /** Creates a view for an element. * * @param elem the element * @return the newly created view or null */ public View create(Element elem) { View v = null; if (elem instanceof BaseElement) { v = new LeafView(elem); } return v; } /** Creates a view for an element. * @param elem the element * @param p0 the starting offset >= 0 * @param p1 the ending offset >= p0 * @return the view */ public View create(Element elem, int p0, int p1) { return new LeafView(elem); } // from JEditorPane ui public static ComponentUI createUI(JComponent c) { return new BaseTextUI(); } /** Specifies that some preference has changed. */ public void preferenceChanged(boolean width, boolean height) { getExtUI().virtualSizeUpdated = false; component.revalidate(); SwingUtilities.invokeLater( new Runnable() { public void run() { Caret caret = component.getCaret(); if (caret instanceof BaseCaret) { ((BaseCaret)caret).dispatchUpdate(); } } } ); } /** Root view */ class RootView extends BaseView { BaseView view; RootView() { super(null); } /** Sets the only one inner view of root view to either null or a valid view. */ void setView(View v) { if (v instanceof BaseView) { // only BaseView instances are supported if (view != null) { // enable grb.col. view.setParent(null); } view = (BaseView)v; if (view != null) { view.setParent(this); } } } /** Fetches attributes associated with view */ public AttributeSet getAttributes() { return null; } /** Determines the preferred span for this view along an axis. * * @param axis may be either X_AXIS or Y_AXIS * @return the span the view would like to be rendered into. */ public float getPreferredSpan(int axis) { ExtUI extUI = BaseTextUI.this.getExtUI(); switch (axis) { case X_AXIS: return Math.max(BaseTextUI.this.getExtUI().virtualSize.width, MIN_WIDTH); case Y_AXIS: return Math.max(BaseTextUI.this.getExtUI().virtualSize.height, MIN_HEIGHT); } return 0f; } /** Determines minimum span along an axis */ public float getMinimumSpan(int axis) { return getPreferredSpan(axis); } /** Determines maximum span along an axis */ public float getMaximumSpan(int axis) { return Integer.MAX_VALUE; } /** Specifies that a preference has changed. */ public void preferenceChanged(View child, boolean width, boolean height) { BaseTextUI.this.preferenceChanged(width, height); } /** Determines the desired alignment for this view along an axis. */ public float getAlignment(int axis) { if (view != null) { return view.getAlignment(axis); } return 0; } /** Renders the view. */ public void paint(Graphics g, Shape allocation) { if (view != null) { view.paint(g, allocation); } } /** Sets the parent view. */ public void setParent(View parent) { // root view has no parent } /** Returns the number of views in this view. */ public int getViewCount() { return 1; } /** Gets the n-th view in this container. */ public View getView(int n) { return view; } /** Fetches the allocation for the given child view. * Returns the whole allocated area. */ public Shape getChildAllocation(int index, Shape a) { return a; } /** Provides a mapping from the document model coordinate space * to the coordinate space of the view mapped to it. */ public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { if (view != null) { return view.modelToView(pos, a, b); } return null; } public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException { if (view != null) { return view.modelToView(p0, b0, p1, b1, a); } return null; } public void modelToViewDG(int pos, Drawer.DrawGraphics dg) throws BadLocationException { if (view != null) { view.modelToViewDG(pos, dg); } } /** Determine next visually represented model location where caret * can be placed. */ public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, int direction, Position.Bias[] biasRet) throws BadLocationException { if (view != null) { int nextPos = view.getNextVisualPositionFrom(pos, b, a, direction, biasRet); if (nextPos != -1) { pos = nextPos; } else { if (biasRet != null) { biasRet[0] = b; } } } return pos; } /** Provides a mapping from the view coordinate space to the logical * coordinate space of the model. */ public int viewToModel(float x, float y, Shape a, Position.Bias[] b) { if (view != null) { return view.viewToModel(x, y, a, b); } return -1; } /** Get y-coord value from position */ protected int getYFromPos(int pos) throws BadLocationException { if (view != null) { return view.getYFromPos(pos); } return 0; } /** Get position when knowing y-coord */ protected int getPosFromY(int y) { if (view != null) { return view.getPosFromY(y); } return -1; } protected int getBaseX(int y) { if (view != null) { return view.getBaseX(y); } return 0; } /** Gives notification that something was inserted into the document * in a location that this view is responsible for. * * @param e the change information from the associated document * @param a the current allocation of the view * @param f the factory to use to rebuild if the view has children */ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) { if (view != null) { view.insertUpdate(e, a, f); } } /** Gives notification that something was removed from the document * in a location that this view is responsible for. * * @param e the change information from the associated document * @param a the current allocation of the view * @param f the factory to use to rebuild if the view has children */ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) { if (view != null) { view.removeUpdate(e, a, f); } } /** * Gives notification from the document that attributes were changed * in a location that this view is responsible for. * * @param e the change information from the associated document * @param a the current allocation of the view * @param f the factory to use to rebuild if the view has children */ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { if (view != null) { view.changedUpdate(e, a, f); } } /** Returns the document model underlying the view. */ public Document getDocument() { return component.getDocument(); } /** Returns the starting offset into the model for this view. */ public int getStartOffset() { if (view != null) { return view.getStartOffset(); } return getElement().getStartOffset(); } /** * Returns the ending offset into the model for this view. * * @return the ending offset */ public int getEndOffset() { if (view != null) { return view.getEndOffset(); } return getElement().getEndOffset(); } /** Gets the element that this view is mapped to. */ public Element getElement() { if (view != null) { return view.getElement(); } return component.getDocument().getDefaultRootElement(); } /** Breaks this view on the given axis at the given length. */ public View breakView(int axis, float len, Shape a) { return null; // no breaking of the root view } /** Determines the resizability of the view along the * given axis. A value of 0 or less is not resizable. */ public int getResizeWeight(int axis) { if (view != null) { return view.getResizeWeight(axis); } return 0; } /** Sets the view size. */ public void setSize(float width, float height) { if (view != null) { view.setSize(width, height); } } /** Fetches the container hosting the view. */ public Container getContainer() { return component; } /** Fetches the factory to be used for building the * various view fragments that make up the view that * represents the model. This is what determines * how the model will be represented. This is implemented * to fetch the factory provided by the associated * EditorKit unless that is null, in which case this * simply returns the BasicTextUI itself which allows * subclasses to implement a simple factory directly without * creating extra objects. * * @return the factory */ public ViewFactory getViewFactory() { EditorKit kit = getEditorKit(component); ViewFactory f = kit.getViewFactory(); if (f != null) { return f; } return BaseTextUI.this; } protected int getStartY() { return BaseTextUI.this.getExtUI().textMargin.top; } protected int getPaintAreas(Graphics g, int clipY, int clipHeight) { return 0; // no paint areas } protected void paintAreas(Graphics g, int clipY, int clipHeight, int paintAreas) { // no painting for the root view } protected int getViewStartY(BaseView view, int helperInd) { return getStartY(); } protected void invalidateStartY() { if (view != null) { view.invalidateStartY(); } } public int getHeight() { if (view != null) { return view.getHeight(); } return 0; } public void updateMainHeight() { if (view != null) { view.updateMainHeight(); ExtUI extUI = BaseTextUI.this.getExtUI(); if (extUI.updateVirtualHeight(getHeight())) { BaseTextUI.this.preferenceChanged(true, true); } } } } private static class GetFocusedComponentAction extends TextAction { private GetFocusedComponentAction() { super("get-focused-component"); } public void actionPerformed(ActionEvent evt) { } JTextComponent getFocusedComponent2() { return super.getFocusedComponent(); } } /** Class that returns back BaseTextUI after its change * by changing look-and-feel. */ static class UIWatcher implements PropertyChangeListener { private Class uiClass; UIWatcher(Class uiClass) { this.uiClass = uiClass; } public void propertyChange(PropertyChangeEvent evt) { if ("UI".equals(evt.getPropertyName()) // NOI18N && !(evt.getNewValue() instanceof BaseTextUI) ) { JTextComponent c = (JTextComponent)evt.getSource(); try { c.setUI((BaseTextUI)uiClass.newInstance()); } catch (InstantiationException e) { } catch (IllegalAccessException e) { } c.removePropertyChangeListener(this); } } } }; /* * Log * 34 Gandalf-post-FCS1.31.1.1 4/3/00 Miloslav Metelka undo update * 33 Gandalf-post-FCS1.31.1.0 3/8/00 Miloslav Metelka * 32 Gandalf 1.31 1/14/00 Miloslav Metelka * 31 Gandalf 1.30 1/13/00 Miloslav Metelka * 30 Gandalf 1.29 12/28/99 Miloslav Metelka * 29 Gandalf 1.28 11/14/99 Miloslav Metelka * 28 Gandalf 1.27 11/8/99 Miloslav Metelka * 27 Gandalf 1.26 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 26 Gandalf 1.25 10/10/99 Miloslav Metelka * 25 Gandalf 1.24 9/16/99 Miloslav Metelka * 24 Gandalf 1.23 9/10/99 Miloslav Metelka * 23 Gandalf 1.22 8/17/99 Miloslav Metelka * 22 Gandalf 1.21 7/26/99 Miloslav Metelka * 21 Gandalf 1.20 7/21/99 Miloslav Metelka * 20 Gandalf 1.19 7/20/99 Miloslav Metelka * 19 Gandalf 1.18 7/2/99 Miloslav Metelka * 18 Gandalf 1.17 6/29/99 Miloslav Metelka Scrolling and patches * 17 Gandalf 1.16 6/25/99 Miloslav Metelka from floats back to ints * 16 Gandalf 1.15 6/8/99 Miloslav Metelka * 15 Gandalf 1.14 6/1/99 Miloslav Metelka * 14 Gandalf 1.13 5/15/99 Miloslav Metelka fixes * 13 Gandalf 1.12 5/7/99 Miloslav Metelka line numbering and fixes * 12 Gandalf 1.11 5/5/99 Miloslav Metelka * 11 Gandalf 1.10 4/23/99 Miloslav Metelka Undo added and internal * improvements * 10 Gandalf 1.9 4/8/99 Miloslav Metelka * 9 Gandalf 1.8 4/1/99 Miloslav Metelka * 8 Gandalf 1.7 3/27/99 Miloslav Metelka * 7 Gandalf 1.6 3/23/99 Miloslav Metelka * 6 Gandalf 1.5 3/18/99 Miloslav Metelka * 5 Gandalf 1.4 3/18/99 Miloslav Metelka * 4 Gandalf 1.3 3/10/99 Jaroslav Tulach Hack to work with * different document than BaseDocument * 3 Gandalf 1.2 2/13/99 Miloslav Metelka * 2 Gandalf 1.1 2/9/99 Miloslav Metelka * 1 Gandalf 1.0 2/3/99 Miloslav Metelka * $ */